﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NAnt.Core;
using System.IO;
using System.Collections;
using System.Globalization;
using NAnt.Core.Util;
using System.Diagnostics;
using System.Threading;

namespace NAntMenu.Logger
{
	public class BuildOutputLogger : NAnt.Core.IBuildLogger
	{
		private Level _threshold = Level.Info;
		private TextWriter _outputWriter;
		private bool _emacsMode;

		/// <summary>
		/// Holds a stack of reports for all running builds.
		/// </summary>
		private readonly Stack _buildReports = new Stack();

		public Semaphore pool;

		public BuildOutputLogger()
		{
			pool = new Semaphore(0, 1);
			Thread t = new Thread(
			  delegate()
			  {
				  BuildOutputDialog buildOutputDialog = new BuildOutputDialog(this);
				  buildOutputDialog.ShowDialog();
			  });
			t.SetApartmentState(ApartmentState.STA);
			t.Start();
			pool.WaitOne();
		}

		public event BuildStartEventHandler BuildStart;
		public event BuildFinishEventHandler BuildFinish;
		public event ErrorEventHandler Error;
		public event WarningEventHandler Warning;
		public event InfoEventHandler Info;

		public delegate void BuildStartEventHandler();
		public delegate void BuildFinishEventHandler(Exception error, int errors, int warnings);
		public delegate void ErrorEventHandler(string message);
		public delegate void WarningEventHandler(string message);
		public delegate void InfoEventHandler(string message);

        #region Implementation of IBuildLogger

        /// <summary>
        /// Gets or sets the highest level of message this logger should respond 
        /// to.
        /// </summary>
        /// <value>
        /// The highest level of message this logger should respond to.
        /// </value>
        /// <remarks>
        /// Only messages with a message level higher than or equal to the given 
        /// level should be written to the log.
        /// </remarks>
        public virtual Level Threshold
        {
            get { return _threshold; }
            set { _threshold = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to produce emacs (and other
        /// editor) friendly output.
        /// </summary>
        /// <value>
        /// <see langword="true" /> if output is to be unadorned so that emacs 
        /// and other editors can parse files names, etc. The default is
        /// <see langword="false" />.
        /// </value>
        public virtual bool EmacsMode
        {
            get { return _emacsMode; }
            set { _emacsMode = value; }
        }

        /// <summary>
        /// Gets or sets the <see cref="TextWriter" /> to which the logger is 
        /// to send its output.
        /// </summary>
        /// <value>
        /// The <see cref="TextWriter" /> to which the logger sends its output.
        /// </value>
        public virtual TextWriter OutputWriter
        {
            get { return _outputWriter; }
            set { _outputWriter = value; }
        }

        /// <summary>
        /// Flushes buffered build events or messages to the underlying storage.
        /// </summary>
        public virtual void Flush()
        {
            if (OutputWriter != null)
            {
                OutputWriter.Flush();
            }
        }

        #endregion Implementation of IBuildLogger

        #region Implementation of IBuildListener

        /// <summary>
        /// Signals that a build has started.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A <see cref="BuildEventArgs" /> object that contains the event data.</param>
        /// <remarks>
        /// This event is fired before any targets have started.
        /// </remarks>
        public virtual void BuildStarted(object sender, BuildEventArgs e)
        {
            _buildReports.Push(new BuildReport(DateTime.Now));

			if (this.BuildStart != null)
			{
				this.BuildStart();
			}
        }

        /// <summary>
        /// Signals that the last target has finished.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A <see cref="BuildEventArgs" /> object that contains the event data.</param>
        /// <remarks>
        /// This event will still be fired if an error occurred during the build.
        /// </remarks>
        public virtual void BuildFinished(object sender, BuildEventArgs e)
        {
            Exception error = e.Exception;
            int indentationLevel = 0;

            if (e.Project != null)
            {
                indentationLevel = e.Project.IndentationLevel * e.Project.IndentationSize;
            }

            BuildReport report = (BuildReport)_buildReports.Pop();

            if (error == null)
            {
				if (this.Info != null)
				{
					if (report.Errors == 0 && report.Warnings == 0)
					{
						this.Info("BUILD SUCCEEDED");
					}
					else
					{
						this.Info(string.Format(CultureInfo.InvariantCulture,
							"BUILD SUCCEEDED - {0} non-fatal error(s), {1} warning(s)",
							report.Errors, report.Warnings));
					}
				}
            }
            else
            {
				if (this.Error != null)
				{
					if (report.Errors == 0 && report.Warnings == 0)
					{
						this.Error("BUILD FAILED");
					}
					else
					{
						this.Error(string.Format(CultureInfo.InvariantCulture,
							"BUILD FAILED - {0} non-fatal error(s), {1} warning(s)",
							report.Errors, report.Warnings));
					}

					if (error is BuildException)
					{
						if (Threshold <= Level.Verbose)
						{
							this.Error(error.ToString());
						}
						else
						{
							if (error.Message != null)
							{
								this.Error(error.Message);
							}

							// output nested exceptions
							Exception nestedException = error.InnerException;
							int exceptionIndentationLevel = indentationLevel;
							int indentShift = 4; //e.Project.IndentationSize;
							while (nestedException != null && !StringUtils.IsNullOrEmpty(nestedException.Message))
							{
								exceptionIndentationLevel += indentShift;
								this.Error(nestedException.Message);
								nestedException = nestedException.InnerException;
							}
						}
					}
					else
					{
						this.Error(string.Format("INTERNAL ERROR: {0}", error.ToString()));
					}
				}
            }

            // output total build time
            TimeSpan buildTime = DateTime.Now - report.StartTime;

			if (this.Info != null)
			{
				this.Info(string.Format(CultureInfo.InvariantCulture,
					"Total time: {0} seconds." + Environment.NewLine,
					Math.Round(buildTime.TotalSeconds, 1)));
			}

            // make sure all messages are written to the underlying storage
            Flush();

			if (this.BuildFinish != null)
			{
				this.BuildFinish(error, report.Errors, report.Warnings);
			}
        }

        /// <summary>
        /// Signals that a target has started.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A <see cref="BuildEventArgs" /> object that contains the event data.</param>
        public virtual void TargetStarted(object sender, BuildEventArgs e)
        {
            int indentationLevel = 0;

            if (e.Project != null)
            {
                indentationLevel = e.Project.IndentationLevel * e.Project.IndentationSize;
            }

            if ((e.Target != null) && (this.Info != null))
            {
                this.Info(string.Format(CultureInfo.InvariantCulture, "{0}:", e.Target.Name));
            }
        }

        /// <summary>
        /// Signals that a task has finished.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A <see cref="BuildEventArgs" /> object that contains the event data.</param>
        /// <remarks>
        /// This event will still be fired if an error occurred during the build.
        /// </remarks>
        public virtual void TargetFinished(object sender, BuildEventArgs e)
        {
        }

        /// <summary>
        /// Signals that a task has started.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A <see cref="BuildEventArgs" /> object that contains the event data.</param>
        public virtual void TaskStarted(object sender, BuildEventArgs e)
        {
        }

        /// <summary>
        /// Signals that a task has finished.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A <see cref="BuildEventArgs" /> object that contains the event data.</param>
        /// <remarks>
        /// This event will still be fired if an error occurred during the build.
        /// </remarks>
        public virtual void TaskFinished(object sender, BuildEventArgs e)
        {
        }

        /// <summary>
        /// Signals that a message has been logged.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A <see cref="BuildEventArgs" /> object that contains the event data.</param>
        /// <remarks>
        /// Only messages with a priority higher or equal to the threshold of 
        /// the logger will actually be output in the build log.
        /// </remarks>
        public virtual void MessageLogged(object sender, BuildEventArgs e)
        {
            if (_buildReports.Count > 0)
            {
                if (e.MessageLevel == Level.Error)
                {
                    BuildReport report = (BuildReport)_buildReports.Peek();
                    report.Errors++;
                }
                else if (e.MessageLevel == Level.Warning)
                {
                    BuildReport report = (BuildReport)_buildReports.Peek();
                    report.Warnings++;
                }
            }

            // output the message
            OutputMessage(e);
        }

        #endregion Implementation of IBuildListener

		protected virtual void Log(string message)
		{
		}

		/// <summary>
		/// Outputs an indented message to the build log if its priority is 
		/// greater than or equal to the <see cref="Threshold" /> of the 
		/// logger.
		/// </summary>
		/// <param name="e">The event to output.</param>
		private void OutputMessage(BuildEventArgs e)
		{
			int indentationLength = 0;

			if (e.Project != null)
			{
				indentationLength = e.Project.IndentationLevel * e.Project.IndentationSize;
			}

			OutputMessage(e, indentationLength);
		}

		/// <summary>
		/// Outputs an indented message to the build log if its priority is 
		/// greater than or equal to the <see cref="Threshold" /> of the 
		/// logger.
		/// </summary>
		/// <param name="e">The event to output.</param>
		/// <param name="indentationLength">The number of characters that the message should be indented.</param>
		private void OutputMessage(BuildEventArgs e, int indentationLength)
		{
			if (e.MessageLevel >= Threshold)
			{
				string txt = e.Message;

				// beautify the message a bit
				txt = txt.Replace("\t", " "); // replace tabs with spaces
				txt = txt.Replace("\r", ""); // get rid of carriage returns

				// split the message by lines - the separator is "\n" since we've eliminated
				// \r characters
				string[] lines = txt.Split('\n');
				string label = String.Empty;

				if (e.Task != null && !EmacsMode)
				{
					label = "[" + e.Task.Name + "] ";
					label = label.PadLeft(e.Project.IndentationSize);
				}

				if (indentationLength > 0)
				{
					label = new String(' ', indentationLength) + label;
				}

				foreach (string line in lines)
				{
					StringBuilder sb = new StringBuilder();
					sb.Append(label);
					sb.Append(line);

					string indentedMessage = sb.ToString();

					// output the message to the console
					switch (e.MessageLevel)
					{
						case Level.Info:
						case Level.None:
						case Level.Verbose:
						case Level.Debug:
							if (this.Info != null)
							{
								this.Info(indentedMessage);
							}
							break;
						case Level.Warning:
							if (this.Warning != null)
							{
								this.Warning(indentedMessage);
							}
							break;
						case Level.Error:
							if (this.Error != null)
							{
								this.Error(indentedMessage);
							}
							break;
					}

					Log(indentedMessage);
				}
			}
		}
	}
}
